﻿#region

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;

#endregion

namespace wsy.ext.flurl.http
{
    public static class Ext
    {
        private static async Task<bool> _DownloadFileAsync(this IFlurlClient client, string downloadDirectory,
            IProgress<DownloadFileProgressInfo> progress, string downloadedFileName = null, int bufferSize = 4096)
        {
            bool autoDispose = client.Settings.AutoDispose;
            client.Settings.AutoDispose = false;
            try
            {
                HttpResponseMessage res = await client.SendAsync(HttpMethod.Get, null, new CancellationToken?(),
                    HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
                if (downloadedFileName == null)
                    if (res.Content.Headers.ContentDisposition != null)
                        downloadedFileName = res.Content.Headers.ContentDisposition.FileName;
                    else
                        downloadedFileName = Path.GetFileName(client.Url.Path);
                if (!Directory.Exists(downloadDirectory))
                    Directory.CreateDirectory(downloadDirectory);
                DownloadFileProgressInfo info = new DownloadFileProgressInfo();
                lock (info)
                {
                    info.FinalDataSize = res.Content.Headers.ContentLength;
                    if (info.FinalDataSize <= 0) info.FinalDataSize = 1;
                }
                ConfiguredTaskAwaitable <Stream> configuredTaskAwaitable =
                    res.Content.ReadAsStreamAsync().ConfigureAwait(false);
                Stream httpStream = await configuredTaskAwaitable;
                try
                {
                    var assembly =
                        AppDomain.CurrentDomain.GetAssemblies()
                            .First(
                                s => s.FullName.StartsWith("Flurl.Http, Version=1.1.1.0, Culture=neutral, PublicKeyToken="));
                    Type[] types = assembly.GetTypes();
                    Type tFileUtil = types.First(s => s.Name == "FileUtil");
                    MethodInfo mFileUtil = tFileUtil.GetMethod("OpenWriteAsync",
                        BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static);
                    Task<Stream> ts =
                        mFileUtil.Invoke(null, new object[] {downloadDirectory, downloadedFileName, bufferSize}) as
                            Task<Stream>;
                    configuredTaskAwaitable = ts.ConfigureAwait(false);
                    Stream filestream = await configuredTaskAwaitable;
                    try
                    {
                        Stopwatch sw = new Stopwatch();
                        byte[] buffer = new byte[bufferSize];
                        while (true)
                        {
                            sw.Reset();
                            sw.Start();
                            int num = await httpStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
                            sw.Stop();
                            if (sw.ElapsedMilliseconds > 0)
                            {
                                lock (info)
                                {
                                    info.TimeTaken = sw.ElapsedMilliseconds;
                                }
                            }
                            int bytesRead;
                            if ((bytesRead = num) != 0)
                            {
                                await filestream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
                                lock (info)
                                {
                                    info.DataSize = bytesRead;
                                    progress.Report(info);
                                }
                            }
                            else
                                break;
                        }
                    }
                    finally
                    {
                        if (filestream != null)
                        {
                        }
                        filestream?.Dispose();
                    }
                }
                finally
                {
                    httpStream.Dispose();
                }
            }
            finally
            {
                client.Settings.AutoDispose = autoDispose;
                if (client.Settings.AutoDispose)
                    client.Dispose();
            }
            return true;
        }

        public static async Task<bool> DownloadFileAsync(this IFlurlClient client, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await _DownloadFileAsync(client, localFolderPath, progress, localFileName, bufferSize);
        }

        public static async Task<bool> DownloadFileAsync(this string url, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await new FlurlClient(url, true)._DownloadFileAsync(localFolderPath, progress, localFileName,
                bufferSize);
        }

        public static async Task<bool> DownloadFileAsync(this Url url, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await new FlurlClient(url, true)._DownloadFileAsync(localFolderPath, progress, localFileName,
                bufferSize);
        }
    }

    public class DownloadFileProgressInfo
    {
        private long _dataSize;
        private long _timeTaken;

        public long TimeTaken
        {
            get { return _timeTaken; }
            set
            {
                _timeTaken = value;
                TotalTimeTaken += value;
            }
        }

        public long TotalTimeTaken { get; private set; }

        public long DataSize
        {
            get { return _dataSize; }
            set
            {
                _dataSize = value;
                TotalDataSize += value;
            }
        }

        public long TotalDataSize { get; private set; }
        public long? FinalDataSize { get; set; }

        public long Speed
        {
            get
            {
                if (TimeTaken == 0) return 0;
                return DataSize *8 / TimeTaken;
            }
        }

        public long TotalSpeed
        {
            get
            {
                if (TotalDataSize == 0 || TotalTimeTaken == 0) return 0;
                return TotalDataSize *8 / TotalTimeTaken;
            }
        }

        public void Clear()
        {
            TimeTaken = 0;
            TotalTimeTaken = 0;
            DataSize = 0;
            TotalDataSize = 0;
            FinalDataSize = 0;
        }
    }
}